//By Francois Budin
#ifndef TabND_cxx
#define TabND_cxx

#include "TabND.h"

namespace FPTK
{

template<class T>
TabND<T>::TabND(int _NbDim,long *_SizeDim)
{
      NbDim=0;
      size=0;
      SizeAddDim=NULL;
      SizeDim=NULL;
      tab=NULL;
      Exception ex("TabND::Constructor","Dimension must be >0");
      if(_NbDim<=0) throw ex;
      NbDim=_NbDim;
      SizeDim=new long[NbDim];
      for(int i=0;i<NbDim;i++)
      {
         SizeDim[i] = *(_SizeDim+i);
      }
      Constructor();
}


template<class T>
TabND<T>::TabND(int _NbDim,long _SizeDim1,...)
{
   try
   {
      NbDim=0;
      size=0;
      SizeAddDim=NULL;
      SizeDim=NULL;
      tab=NULL;
      Exception ex("TabND::Constructor","Dimension must be >0");
      if(_NbDim<=0) throw ex;
      NbDim=_NbDim;
      SizeDim=new long[NbDim];
      SizeDim[0]=_SizeDim1;
      va_list ap;
      va_start(ap,_SizeDim1);
      for(int i=1;i<NbDim;i++)
      {
         SizeDim[i] = va_arg(ap, long);
      }
      va_end(ap);
      Constructor();
   }
   catch(std::bad_alloc &ba)
   {
      Exception ex("TabND::Constructor:",ba.what());
      throw ex;
   }
}


template<class T>
const std::string TabND<T>::GetType() const
{
   return type;
}

template<class T>
void TabND<T>::Constructor()
{
   try{
      type=typeid(T).name();
      bool ok=1;
      Exception ex2("TabND::Constructor","Sizes must be >0");
      Exception ex3("TabND::Constructor","Overflow:Dimensions too big");
      for(int i=0;i<NbDim;i++)
      {
         if(SizeDim[i]<=0)
         {
            ok=0;
            break;
         }
      }
      if(ok==0)
      {
         for(int i=0;i<NbDim;i++)
         {
            SizeDim[i]=-1;
         }
         throw ex2;
      }
      SizeAddDim=new unsigned long[NbDim];
      SizeAddDim[0]=1;
      for(int i=1;i<NbDim;i++)
      {
         SizeAddDim[i]=SizeAddDim[i-1]*unsigned long(SizeDim[i-1]);
         if(SizeAddDim[i]/SizeAddDim[i-1]<unsigned long(SizeDim[i-1])) throw ex3;

      }
      size=1;
      size=unsigned long(SizeDim[NbDim-1])*SizeAddDim[NbDim-1];
      if(size*unsigned long(sizeof(T))/unsigned long(SizeDim[NbDim-1])<SizeAddDim[NbDim-1]*unsigned long(sizeof(T))) throw ex3;
      tab=new T[size];
   }
   catch(std::bad_alloc &ba)
   {
      Exception ex4("TabND::Constructor:",ba.what());
      throw ex4;
   }
}




template<class T>
TabND<T>::~TabND()
{
   delete []SizeDim;
   delete []SizeAddDim;
   delete []tab;
}

//template<class T> template<class C>
//TabND<C> TabND<T>::template Cast()
//{
//TabND<C> tmp(GetNbDim(),SizeDim);
//for(long i=0;i<GetSizeDim(0);i++)
//{
//   for(long j=0;j<GetSizeDim(1);j++)
//   {
//   tmp.SetElement((int)GetElement(i,j),i,j);
//   }
//}
//return tmp;
//}

template<class T> template<class C>
TabND<T>::operator TabND<C> const() const
{
TabND<C> tmp(GetNbDim(),SizeDim);
for(long i=0;i<GetSizeDim(0);i++)
{
   for(long j=0;j<GetSizeDim(1);j++)
   {
   tmp.SetElement((C)GetElement(i,j),i,j);
   }
}
return tmp;
}



//template<class T> template<class C>
//TabND<T>::operator TabND<C>()
//{
//TabND<C> tmp(GetNbDim(),SizeDim);
//for(unsigned long i=0;i<size;i++)
//{
//   tmp.SetElement((int)GetElement(i),i);
//}
//return tmp;
//}


//template<class T>
//TabND<T>::operator TabND<int>()
//{
//TabND<int> tmp(GetSizeDim(0),GetSizeDim(1));
//for(long i=0;i<GetSizeDim(0);i++)
//{
//   for(long j=0;j<GetSizeDim(1);j++)
//   {
//   tmp.SetElement((int)GetElement(i,j),i,j);
//   }
//}
//return tmp;
//}



template<class T>
TabND<T>::TabND(const TabND<T>& t)
{
   try{
      NbDim=t.GetNbDim();
      size=t.GetSizeAddDim(NbDim-1)*unsigned long(t.GetSizeDim(NbDim-1));
      SizeDim=new long[NbDim];
      SizeAddDim=new unsigned long[NbDim];
      for(int i=0;i<NbDim;i++)
      {
         SizeDim[i]=t.GetSizeDim(i);
         SizeAddDim[i]=t.GetSizeAddDim(i);
      }
      tab=new T[size];
      for(unsigned long i=0;i<size;i++)
      {
         SetElement(t.GetElement(i),i);
      }
   }
   catch(std::bad_alloc &ba)
   {
      //std::cout<<"TabND::Constructor:"<<ba.what()<<std::endl;
       Exception ex("TabND::Constructor:",ba.what());
       throw ex;
   }
}


template<class T>
const T TabND<T>::GetElement(long pos1,long pos2,...) const
{  
   unsigned long pos=0;
   try{
      Exception ex1("TabND::GetElement","The table doesn\'t have 2 dimensions or more");
      Exception ex2("TabND::GetElement","Position must be positive and smaller than the size of the Tab");
      if(NbDim<=1) throw ex1;
      long* position=new long[NbDim];
      position[0]=pos1;
      position[1]=pos2;
      va_list ap;
      va_start(ap,pos2);
      for(int i=2;i<NbDim;i++)
      {
         position[i] = va_arg(ap, long);
      }
      va_end(ap);
      for(int i=0;i<NbDim;i++)
      {
         if(position[i]<0 ||position[i]>SizeDim[i]) throw ex2;
      }
      for(int i=0;i<NbDim;i++)
      {
         pos+=unsigned long(position[i])*SizeAddDim[i];
      }
      delete []position;
   }
   catch(std::bad_alloc &ba)
   {
      //std::cout<<"TabND::Constructor:"<<ba.what()<<std::endl;
       //char *bawhat=new char[strlen(ba.what())+1];
       //ba.what();
       Exception ex("TabND::Constructor:",ba.what());
       throw ex;
   }
   return GetElement(pos);
}

template<class T>
const T TabND<T>::GetElement(unsigned long pos) const
{
Exception ex("TabND::GetElement","Position is outside the table");
if(pos<0 || pos>=size) throw ex;
return *(tab+pos);
}


template<class T>
void TabND<T>::SetElement(T value,long pos1,long pos2,...)
{
   try
   {
      Exception ex1("TabND::GetElement","The table doesn\'t have 2 dimensions or more");
      Exception ex2("TabND::GetElement","Position must be positive and smaller than the size of the Tab");
      if(NbDim<=1) throw ex1;
      long* position=new long[NbDim];
      position[0]=pos1;
      position[1]=pos2;
      va_list ap;
      va_start(ap,pos2);
      for(int i=2;i<NbDim;i++)
      {
         position[i] = va_arg(ap, long);
      }
      va_end(ap);
      for(int i=0;i<NbDim;i++)
      {
         if(position[i]<0 ||position[i]>SizeDim[i]) throw ex2;
      }
      unsigned long pos=0;
      for(int i=0;i<NbDim;i++)
      {
         pos+=unsigned long(position[i])*SizeAddDim[i];
      }
      delete []position;
      SetElement(value,pos);
   }
   catch(std::bad_alloc &ba)
   {
      //std::cout<<"TabND::Constructor:"<<ba.what()<<std::endl;
       Exception ex("TabND::Constructor:",ba.what());
       throw ex;
   }
}

template<class T>
void TabND<T>::SetElement(T value,unsigned long pos)
{
Exception ex("TabND::GetElement","Position is outside the table");
if(pos<0 || pos>=size) throw ex;
*(tab+pos)=value;
}



template<class T>
void TabND<T>::Zeros()
{
Fill(T(0));
}

template<class T>
void TabND<T>::Fill(T _var)
{
   for(unsigned long i=0;i<size;i++)
   {
      SetElement(_var,i);
   }
}


template<class T>
const int TabND<T>::GetNbDim() const
{
    return NbDim;
}


template<class T>
const long TabND<T>::GetSizeDim(int i) const
{
    return SizeDim[i];
}

template<class T>
const unsigned long TabND<T>::GetSizeAddDim(int i) const
{
    return SizeAddDim[i];
}


template<class T>
bool TabND<T>::operator==(const TabND<T>& t2) const
{
   Exception ex("Tab2::operator==","Tables doesn\'t have the same size");
   if(t2.GetNbDim) throw ex;
   for(int i=0;i<NbDim;i++)
   {
      if(t2.GetSizeDim(i)!=SizeDim[i]) throw ex;
   }
   for(unsigned long i=0;i<size;i++)
   {
      if(GetElement(i)!=t2.GetElement(i)) return false;
   }
   return true;
}

template<class T>
bool TabND<T>::operator!=(const TabND<T>& t2) const
{
   return ~((*this)==t2);
}

template<class T>
TabND<T> TabND<T>::operator=(const TabND<T>& t2)
{
   Exception ex("Tab2::operator=","Tables doesn\'t have the same size");
   if(t2.GetNbDim()!=NbDim) throw ex;
   for(int i=0;i<NbDim;i++)
   {
      if(t2.GetSizeDim(i)!=SizeDim[i]) throw ex;
   }
   for(unsigned long i=0;i<size;i++)
   {
          SetElement(t2.GetElement(i),i);
   }
   return *this;
}


template<class T>
TabND<T> TabND<T>::RemoveLineTabND(int numdim,long numline) const
{
Exception ex("Tab2::RemoveLineTabND","Dimension to reduce doesn\'t exist");
Exception ex2("Tab2::RemoveLineTabND","Dimension cannot be reduce");
Exception ex3("Tab2::RemoveLineTabND","Line to remove doesn\'t exist");
if(numdim<0 || numdim>=NbDim)
{
   throw ex;
}
if(SizeDim[numdim]<2)
{
   throw ex2;
}
if(numline>=SizeDim[numdim])
{
   throw ex3;
}



}

template<class T>
TabND<T> TabND<T>::ResizeTabND(int numdim,long infirst,long inlast,long newsize) const
{
Exception ex("Tab2::ResizeTabND","Size must be >0");
int NbDim_tmp;
long *SizeDim_tmp;
if(newsize<=0)
{
   throw ex;
}
else
{
   if(newsize==1)
   {
      NbDim_tmp=NbDim-1;
      SizeDim=new long[NbDim_tmp];
      for(int i=0;i<NbDim;i++)
      {
         if(i<numdim)
         {
            SizeDim_tmp[i]=SizeDim[i];
         }
         else
         {
            SizeDim_tmp[i-1]=SizeDim[i];
         }
      }
   }
   else
   {
      NbDim_tmp=NbDim;
      SizeDim=new long[NbDim_tmp];
      for(int i=0;i<NbDim;i++)
      {
         NbDim_tmp[i]=NbDim[i];
      }
   }

   TabNB<T> tmp(NbDim_tmp,SizeDim_tmp);




   delete []SizeDim;
   return tmp;
}
}


}//end namespace FPTK

#endif